#include "Model.hpp"

#include <iostream>

using namespace std;
namespace Data {

void ModelObject::CreateMesh(const Vertices &vertices, const Indices &indices) {
  _meshes.push_back(make_shared<Mesh>(Mesh(vertices, indices)));
}

void MeshLoader::ProcessMesh(aiMesh *mesh, const aiScene *scene) {
  // Data to fill
  vector<Vertex> vertices;
  vector<uint32_t> indices;
  // vector<Texture> textures;

  // Walk through each of the mesh's vertices
  for (uint32_t i = 0; i < mesh->mNumVertices; i++) {
    Vertex vertex;
    glm::vec3 vector;

    // Positions
    vector.x = mesh->mVertices[i].x;
    vector.y = mesh->mVertices[i].y;
    vector.z = mesh->mVertices[i].z;
    vertex.position = vector;

    // Normals
    vector.x = mesh->mNormals[i].x;
    vector.y = mesh->mNormals[i].y;
    vector.z = mesh->mNormals[i].z;
    vertex.normal = vector;

    // Texture Coordinates
    if (mesh->mTextureCoords[0]) {
      // Check if the mesh contains texture coordinates
      glm::vec2 vec;
      vec.x = mesh->mTextureCoords[0][i].x;
      vec.y = mesh->mTextureCoords[0][i].y;
      vertex.texCoords = vec;
    } else {
      vertex.texCoords = glm::vec2(0.0f, 0.0f);
    }

    if (mesh->HasTangentsAndBitangents()) {
      // Tangent
      vector.x = mesh->mTangents[i].x;
      vector.y = mesh->mTangents[i].y;
      vector.z = mesh->mTangents[i].z;
      vertex.tangent = vector;
      // Bitangent
      vector.x = mesh->mBitangents[i].x;
      vector.y = mesh->mBitangents[i].y;
      vector.z = mesh->mBitangents[i].z;
      vertex.biTangent = vector;
    }
    // Push onto the vector of vertices
    vertices.push_back(vertex);
  }

  // Loop through each of the mesh's faces and get its vertex indices
  for (uint32_t i = 0; i < mesh->mNumFaces; i++) {
    aiFace face = mesh->mFaces[i];
    for (uint32_t j = 0; j < face.mNumIndices; j++) {
      indices.push_back(face.mIndices[j]);
    }
  }

  // Process materials
  // if (mesh->mMaterialIndex >= 0) {
  // aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
  // Sssume a convention for sampler names in the shaders. Each diffuse texture should
  // be named as 'texture_diffuseN' where N is a sequential number ranging from 1 to
  // MAX_SAMPLER_NUMBER. Same applies to other texture as the following list summarizes:
  // Diffuse: texture_diffuseN
  // Specular: texture_specularN
  // Normal: texture_normalN

  // Diffuse maps
  // vector<Texture> diffuseMaps =
  //     this->LoadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
  // textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());

  // // Specular maps
  // vector<Texture> specularMaps =
  //     this->LoadMaterialTextures(material, aiTextureType_SPECULAR,
  //     "texture_specular");
  // textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());

  // // Normal maps
  // std::vector<Texture> normalMaps =
  //     this->LoadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
  // textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());

  // // Height maps
  // std::vector<Texture> heightMaps =
  //     this->LoadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height");
  // textures.insert(textures.end(), heightMaps.begin(), heightMaps.end());
  // }

  _model->CreateMesh(vertices, indices);
}

void MeshLoader::ProcessNode(aiNode *node, const aiScene *scene) {
  for (uint32_t i = 0; i < node->mNumMeshes; i++) {
    aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
    this->ProcessMesh(mesh, scene);
  }

  // After we've processed all of the meshes (if any) we then recursively process each of
  // the children nodes
  for (uint32_t i = 0; i < node->mNumChildren; i++) {
    this->ProcessNode(node->mChildren[i], scene);
  }
}

void MeshLoader::LoadMeshFromFile(const char *fileName) {
  Assimp::Importer importer;
  const aiScene *scene = importer.ReadFile(
      fileName, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);

  if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
    cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
    return;
  }

  ProcessNode(scene->mRootNode, scene);
}

} // namespace Data